﻿namespace PugLib
{
	using System;
	using System.Collections.Generic;
	using System.Linq;

	/// <summary>
	/// Default implementation of the event aggregator pattern:
	/// http://martinfowler.com/eaaDev/EventAggregator.html
	/// </summary>
	public class EventAggregator : IEventAggregator
	{
		/// <summary>
		/// The _dispatcher.
		/// </summary>
		private readonly IDispatcher _dispatcher;

		/// <summary>
		/// The _listener lock.
		/// </summary>
		private readonly object _listenerLock = new object();

		/// <summary>
		/// The _listeners.
		/// </summary>
		private readonly Dictionary<Type, List<IListener>> _listeners = new Dictionary<Type, List<IListener>>();

		/// <summary>
		/// Initializes a new instance of the <see cref="EventAggregator"/> class.
		/// </summary>
		/// <param name="dispatcher">
		/// The dispatcher.
		/// </param>
		public EventAggregator(IDispatcher dispatcher)
		{
			_dispatcher = dispatcher;
		}

		#region IEventAggregator Members

		/// <summary>
		/// The subscribe.
		/// </summary>
		/// <param name="listener">
		/// The listener.
		/// </param>
		public virtual void Subscribe(IListener listener)
		{
			ForEachListenerInterfaceImplementedBy(listener, Subscribe);
		}

		/// <summary>
		/// The unsubscribe.
		/// </summary>
		/// <param name="listener">
		/// The listener.
		/// </param>
		public virtual void Unsubscribe(IListener listener)
		{
			ForEachListenerInterfaceImplementedBy(listener, Unsubscribe);
		}

		/// <summary>
		/// The subscribe.
		/// </summary>
		/// <param name="listener">
		/// The listener.
		/// </param>
		/// <typeparam name="TMessage">
		/// </typeparam>
		public virtual void Subscribe<TMessage>(IListener<TMessage> listener) where TMessage : Message
		{
			Subscribe(typeof(TMessage), listener);
		}

		/// <summary>
		/// The unsubscribe.
		/// </summary>
		/// <param name="listener">
		/// The listener.
		/// </param>
		/// <typeparam name="TMessage">
		/// </typeparam>
		public virtual void Unsubscribe<TMessage>(IListener<TMessage> listener) where TMessage : Message
		{
			Unsubscribe(typeof(TMessage), listener);
		}

		/// <summary>
		/// The publish.
		/// </summary>
		/// <param name="message">
		/// The message.
		/// </param>
		/// <typeparam name="TMessage">
		/// </typeparam>
		public virtual void Publish<TMessage>(TMessage message) where TMessage : Message
		{
			Type typeOfEvent = typeof(TMessage);
			IListener[] listeners;
			lock (_listenerLock)
			{
				List<IListener> tmp;
				if (!_listeners.TryGetValue(typeOfEvent, out tmp))
				{
					return;
				}

				listeners = tmp.ToArray();
			}

			foreach (IListener listener in listeners)
			{
				IListener<TMessage> typedReference = (IListener<TMessage>) listener;
				_dispatcher.Post(() => typedReference.Receive(message));
			}
		}

		#endregion

		/// <summary>
		/// The for each listener interface implemented by.
		/// </summary>
		/// <param name="listener">
		/// The listener.
		/// </param>
		/// <param name="action">
		/// The action.
		/// </param>
		private static void ForEachListenerInterfaceImplementedBy(IListener listener, Action<Type, IListener> action)
		{
			string listenerTypeName = typeof(IListener).Name;
			IEnumerable<Type> types = listener.GetType().GetInterfaces().Where(i => i.Name.StartsWith(listenerTypeName, StringComparison.OrdinalIgnoreCase));
			foreach (Type interfaceType in types)
			{
				Type typeOfEvent = GetEventType(interfaceType);
				if (typeOfEvent != null)
				{
					action(typeOfEvent, listener);
				}
			}
		}

		/// <summary>
		/// The get event type.
		/// </summary>
		/// <param name="type">
		/// The type.
		/// </param>
		/// <returns>
		/// </returns>
		private static Type GetEventType(Type type)
		{
			Type[] arguments = type.GetGenericArguments();
			return arguments.Length > 0 ? arguments[0] : null;
		}

		/// <summary>
		/// The subscribe.
		/// </summary>
		/// <param name="typeOfEvent">
		/// The type of event.
		/// </param>
		/// <param name="listener">
		/// The listener.
		/// </param>
		/// <exception cref="InvalidOperationException">
		/// </exception>
		protected virtual void Subscribe(Type typeOfEvent, IListener listener)
		{
			lock (_listenerLock)
			{
				List<IListener> listeners;
				if (!_listeners.TryGetValue(typeOfEvent, out listeners))
				{
					listeners = new List<IListener>();
					_listeners.Add(typeOfEvent, listeners);
				}

				if (listeners.Contains(listener))
				{
					throw new InvalidOperationException("You're not supposed to register to the same event twice");
				}

				listeners.Add(listener);
			}
		}

		/// <summary>
		/// The unsubscribe.
		/// </summary>
		/// <param name="typeOfEvent">
		/// The type of event.
		/// </param>
		/// <param name="listener">
		/// The listener.
		/// </param>
		protected virtual void Unsubscribe(Type typeOfEvent, IListener listener)
		{
			lock (_listenerLock)
			{
				if (_listeners.ContainsKey(typeOfEvent))
				{
					_listeners[typeOfEvent].Remove(listener);
				}
			}
		}

		/// <summary>
		/// Creates a default event aggregator based on the calling thread's context.
		/// </summary>
		/// <returns>
		/// </returns>
		public static EventAggregator CreateDefault()
		{
			return new EventAggregator(Dispatcher.CreateDefault());
		}
	}

	/// <summary>
	/// The i dispatcher.
	/// </summary>
	public interface IDispatcher
	{
		/// <summary>
		/// The post.
		/// </summary>
		/// <param name="action">
		/// The action.
		/// </param>
		void Post(Action action);
	}

	/// <summary>
	/// http://martinfowler.com/eaaDev/EventAggregator.html
	/// </summary>
	public interface IEventAggregator
	{
		/// <summary>
		/// The publish.
		/// </summary>
		/// <param name="message">
		/// The message.
		/// </param>
		/// <typeparam name="TMessage">
		/// </typeparam>
		void Publish<TMessage>(TMessage message) where TMessage : Message;

		/// <summary>
		/// The subscribe.
		/// </summary>
		/// <param name="listener">
		/// The listener.
		/// </param>
		void Subscribe(IListener listener);

		/// <summary>
		/// The unsubscribe.
		/// </summary>
		/// <param name="listener">
		/// The listener.
		/// </param>
		void Unsubscribe(IListener listener);

		/// <summary>
		/// The subscribe.
		/// </summary>
		/// <param name="listener">
		/// The listener.
		/// </param>
		/// <typeparam name="TMessage">
		/// </typeparam>
		void Subscribe<TMessage>(IListener<TMessage> listener) where TMessage : Message;

		/// <summary>
		/// The unsubscribe.
		/// </summary>
		/// <param name="listener">
		/// The listener.
		/// </param>
		/// <typeparam name="TMessage">
		/// </typeparam>
		void Unsubscribe<TMessage>(IListener<TMessage> listener) where TMessage : Message;
	}

	/// <summary>
	/// Not meant to be used directly.
	/// This derrives from IDisposable to remind users to unsubscribe on dispose.
	/// </summary>
	public interface IListener : IDisposable
	{
		/// <summary>
		/// Implementers of IListener<typeparamref name="TMessage"/> aren't forced to unsubscribe/stop listening for events,
		/// but this base interface and method stub encourges them to unsubscribe on dispose.
		/// </summary>
		void StopListening();
	}

	/// <summary>
	/// The i listener.
	/// </summary>
	/// <typeparam name="TMessage">
	/// </typeparam>
	public interface IListener<TMessage> : IListener
		where TMessage : Message
	{
		/// <summary>
		/// The receive.
		/// </summary>
		/// <param name="receivedMessage">
		/// The received event.
		/// </param>
		void Receive(TMessage receivedMessage);
	}

	/// <summary>
	/// Base class for the EventAggregator events
	/// </summary>
	[Serializable]
	public abstract class Message
	{
	}
}